package com.syezon.android.base.download;

import android.content.Context;
import android.util.Log;

import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;

public class DownloadManager implements IDownloadManager {
    private static final String TAG = "DownloadManager";
    private static DownloadManager downloadManager;
    private DownloadInfoDao downloadInfoDao;
    private Context context;
    private int mPoolSize = 5;
    private ExecutorService executorService;
    private Map<String, Future> futureMap;
    private Map<String, DownloadTask> currentTaskList = new HashMap<>();

    private DownloadManager() {
        init();
    }

    private DownloadManager(Context context, InputStream in) {
        this.context = context;
        init();
    }

    public static SSLSocketFactory initCertificates(InputStream... certificates) {
        CertificateFactory certificateFactory;
        SSLContext sslContext = null;
        try {
            certificateFactory = CertificateFactory.getInstance("X.509");
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null);
            int index = 0;
            for (InputStream certificate : certificates) {
                String certificateAlias = Integer.toString(index++);
                keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));

                try {
                    if (certificate != null)
                        certificate.close();
                } catch (IOException e) {
                }
            }

            sslContext = SSLContext.getInstance("TLS");

            TrustManagerFactory trustManagerFactory =
                    TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());

            trustManagerFactory.init(keyStore);
            sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (sslContext != null) {
            return sslContext.getSocketFactory();
        }
        return null;

    }

    /**
     * @param context
     * @param sslKey  https签名文件
     * @return
     */
    public static DownloadManager getInstance(Context context, InputStream sslKey) {
        if (downloadManager == null) {
            synchronized (DownloadManager.class) {
                if (downloadManager == null) {
                    downloadManager = new DownloadManager(context, sslKey);
                }
            }
        }
        return downloadManager;
    }

    public static DownloadManager getInstance(Context context) {
        return getInstance(context, null);
    }


    public Map<String, DownloadTask> getCurrentTaskList() {
        return currentTaskList;
    }


    private void init() {
        executorService = Executors.newFixedThreadPool(mPoolSize);
        futureMap = new HashMap<>();
        downloadInfoDao = new DownloadInfoDao(context);
    }

    /**
     * if task already exist,return the task,else return null
     *
     * @param task
     * @param listener
     * @return
     */
    public DownloadTask addDownloadTask(DownloadTask task, IDownloadTaskListener listener) {
        DownloadTask downloadTask = currentTaskList.get(task.getId());
        if (null != downloadTask) {
            if (downloadTask.getDownloadStatus() == DownloadStatus.DOWNLOAD_STATUS_PAUSE || downloadTask.getDownloadStatus() == DownloadStatus.DOWNLOAD_STATUS_DOWNLOADING) {
                Log.d(TAG, "task already exist");
                return downloadTask;

            } else {
                currentTaskList.remove(task.getId());
            }
        }
        currentTaskList.put(task.getId(), task);
        task.setDownloadStatus(DownloadStatus.DOWNLOAD_STATUS_PREPARE);
        task.setDownloadDao(downloadInfoDao);
        task.addDownloadListener(listener);
        DownloadTask dbDownloadTask = getDBTaskById(task.getId());
        if (dbDownloadTask == null) {
            DownloadInfo dbEntity = new DownloadInfo(task.getId(), task.getTotalSize(), task.getCompletedSize(), task.getUrl(), task.getSaveDirPath(), task.getFileName(), task.getDownloadStatus());
            downloadInfoDao.update(dbEntity);
        }
        Future future = executorService.submit(task);
        futureMap.put(task.getId(), future);
        return null;
    }

    /**
     * if return null,the task does not exist
     *
     * @param taskId
     * @return
     */
    public DownloadTask resume(String taskId) {
        DownloadTask downloadTask = getCurrentTaskById(taskId);
        if (downloadTask != null) {
            if (downloadTask.getDownloadStatus() == DownloadStatus.DOWNLOAD_STATUS_PAUSE) {
                downloadTask.setDownloadStatus(DownloadStatus.DOWNLOAD_STATUS_INIT);
                Future future = executorService.submit(downloadTask);
                futureMap.put(downloadTask.getId(), future);
            }
        } else {
            downloadTask = getDBTaskById(taskId);
            if (downloadTask != null) {
                downloadTask.setDownloadStatus(DownloadStatus.DOWNLOAD_STATUS_INIT);
                currentTaskList.put(taskId, downloadTask);
                Future future = executorService.submit(downloadTask);
                futureMap.put(downloadTask.getId(), future);
            }
        }
        return downloadTask;
    }


    public void addDownloadListener(DownloadTask task, IDownloadTaskListener listener) {
        task.addDownloadListener(listener);
    }

    public void removeDownloadListener(DownloadTask task, IDownloadTaskListener listener) {
        task.removeDownloadListener(listener);
    }

    public DownloadTask addDownloadTask(DownloadTask task) {
        return addDownloadTask(task, null);
    }

    public void cancel(DownloadTask task) {
        task.cancel();
        currentTaskList.remove(task.getId());
        futureMap.remove(task.getId());
        task.setDownloadStatus(DownloadStatus.DOWNLOAD_STATUS_CANCEL);
        downloadInfoDao.remove(task.getId());
    }

    public void cancel(String taskId) {
        DownloadTask task = getTaskById(taskId);
        if (task != null) {
            cancel(task);
        }
    }

    public void pause(DownloadTask task) {
        task.setDownloadStatus(DownloadStatus.DOWNLOAD_STATUS_PAUSE);
    }

    public void pause(String taskId) {
        DownloadTask task = getTaskById(taskId);
        if (task != null) {
            pause(task);
        }
    }


    public List<DownloadInfo> loadAllDownloadEntityFromDB() {
        return downloadInfoDao.findAll();
    }

    /**
     * if not exists return null
     * the task maybe not in the running task list,
     * you can add{@link #addDownloadTask(DownloadTask) addDownloadTask}
     *
     * @return
     */
    public List<DownloadTask> loadAllDownloadTaskFromDB() {
        List<DownloadInfo> list = loadAllDownloadEntityFromDB();
        List<DownloadTask> downloadTaskList = null;
        if (list != null && !list.isEmpty()) {
            downloadTaskList = new ArrayList<>();
            for (DownloadInfo entity : list) {
                downloadTaskList.add(DownloadTask.parse(entity));
            }
        }
        return downloadTaskList;
    }

    /**
     * return all task include running and db
     *
     * @return
     */
    public List<DownloadTask> loadAllTask() {
        List<DownloadTask> list = loadAllDownloadTaskFromDB();
        Map<String, DownloadTask> currentTaskMap = getCurrentTaskList();
        List<DownloadTask> currentList = new ArrayList<>();
        if (currentTaskMap != null) {
            currentList.addAll(currentTaskMap.values());
        }
        if (!currentList.isEmpty() && list != null) {
            for (DownloadTask task : list) {
                if (!currentList.contains(task)) {
                    currentList.add(task);
                }
            }
        } else {
            if (list != null) currentList.addAll(list);
        }
        return currentList;
    }

    /**
     * return the task in the running task list
     *
     * @param taskId
     * @return
     */
    public DownloadTask getCurrentTaskById(String taskId) {
        return currentTaskList.get(taskId);
    }

    /**
     * if not exists return null
     * the task maybe not in the running task list,
     * you can add{@link #addDownloadTask(DownloadTask) addDownloadTask}
     *
     * @param taskId
     * @return
     */
    public DownloadTask getTaskById(String taskId) {
        DownloadTask task = null;
        task = getCurrentTaskById(taskId);
        if (task != null) {
            return task;
        }
        return getDBTaskById(taskId);
    }

    /**
     * if not exists return null
     * the task maybe not in the running task list,
     * you can add{@link #addDownloadTask(DownloadTask) addDownloadTask}
     *
     * @param taskId
     * @return
     */
    public DownloadTask getDBTaskById(String taskId) {
        DownloadInfo entity = downloadInfoDao.find(taskId);
        if (entity != null) {
            return DownloadTask.parse(entity);
        }
        return null;
    }

}
